/*
 * Copyright 2010-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.code.newpath.core.beanresource;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import com.google.code.newpath.aop.AopInstanceEvent;
import com.google.code.newpath.config.BeanResourceConfig;
import com.google.code.newpath.config.ConfigException;
import com.google.code.newpath.core.Context;
import com.google.code.newpath.core.Path;
import com.google.code.newpath.core.Resource;
import com.google.code.newpath.core.ResourceConfig;
import com.google.code.newpath.core.annotation.ContextIn;
import com.google.code.newpath.core.annotation.Lifecycle;
import com.google.code.newpath.core.annotation.ResourceIn;
import com.google.code.newpath.core.annotation.TargetIn;
import com.google.code.newpath.core.exception.ResourceAccessException;
import com.google.code.newpath.core.exception.ResourceInstanceException;
import com.google.code.newpath.core.exception.SysRuntimeException;
import com.google.code.newpath.core.impl.BaseResource;
import com.google.code.newpath.util.ReflectionUtils;
import com.google.code.newpath.util.ValueUtils;
import com.google.code.newpath.core.ResourceInstanceEvent;
/**
 * 
 * @author Charlie Zhang
 *
 */
public class BeanResource extends BaseResource<Object> {
	
	private String className;
	
	private Class<?> clazz;
	
	//用来存储配置信息，如ContextIn, TargetIn, ResourceIn, DestroyMethod, initMehtod 等, 这些信息可以来自配置文件，也可以是Annotation。
    //这样储存主要用于统一配置的程序处理入口，同时也减少多次重复分析的消耗，加速多次处理的速度。
    //属性的设置..., 所有配置的
    //AOP部分的加载和初始化
	private BeanResourceConfig config;
	
	//Internal using to cache configuration parse object and fasten the next processing.
	private BeanResourceConfigCache configCache;
	
	private static List<ResourceInstanceEvent> events;
	
	static {
		events = new ArrayList<ResourceInstanceEvent>();
		events.add(new AopInstanceEvent());
		events.add(new AnnotationInstanceEvent());
		events.add(new ConfigInstanceEvent());
	}
	
	static class BeanResourceConfigCache {
		private Method initMethod;
		private Method destroyMethod;
		public Method getInitMethod() {
			return initMethod;
		}
		public void setInitMethod(Method initMethod) {
			this.initMethod = initMethod;
		}
		public Method getDestroyMethod() {
			return destroyMethod;
		}
		public void setDestroyMethod(Method destroyMethod) {
			this.destroyMethod = destroyMethod;
		}
	}
	
	public BeanResource(BeanResourceCreator creator, Path path, String className) throws ResourceInstanceException {
		super(events);
		
		setCreator(creator);
		setPath(path);
		this.className = className;
		setScope(parseBeanScope());
		
		try {
			this.clazz = creator.getClassLoader().loadClass(className);
		}
		catch(ClassNotFoundException cnfe) {
			throw new ResourceInstanceException(cnfe, this);
		}
	}
	
	
	public BeanResource(BeanResourceCreator creator, BeanResourceConfig resourceConfig) throws ResourceInstanceException {
		super(events);
		
		setCreator(creator);
		setPath(resourceConfig.getPath());
		this.className = resourceConfig.getClassName();
		setScope(resourceConfig.getScope());
		this.config = resourceConfig;
		this.configCache = new BeanResourceConfigCache();
		
		try {
			this.clazz = creator.getClassLoader().loadClass(className);
		}
		catch(ClassNotFoundException cnfe) {
			throw new ResourceInstanceException(cnfe, this);
		}
	}
	
	
	protected Object doInstanceTarget(Context context)  {
		try {
			return clazz.newInstance();
		} 
		catch (InstantiationException ie) 
		{
			throw new ResourceInstanceException(ie.getMessage(), ie, this);
		} 
		catch (IllegalAccessException iae) {
			throw new ResourceInstanceException(iae, this);
		}
	}
	

	private String parseBeanScope() {
		try {
			BeanResourceCreator beanFactory = (BeanResourceCreator)getCreator();
			Class<?> clazz = beanFactory.getClassLoader().loadClass(className);
			Lifecycle lifecycle = clazz.getAnnotation(Lifecycle.class);
			if(lifecycle != null) {
				return lifecycle.scope();
			}
			else {
				return beanFactory.getDefaultScope();
			}
		}
		catch(ClassNotFoundException cnfe) {
			throw new ResourceInstanceException(cnfe, this);
		}
	}

	public ResourceConfig getConfig() {
		return config;
	}
	

	protected void afterInstanceTarget(Context context, Object target) {
		
		if(config != null && ValueUtils.notEmpty(config.getInitMethod())) {
			if(configCache.getInitMethod() == null) {
				try {
					configCache.setInitMethod(target.getClass().getMethod( config.getInitMethod()));
				} catch (NoSuchMethodException e) {
					throw new ConfigException("Failed to found the initMethod " +  config.getInitMethod() + "() in the class '" + target.getClass().getCanonicalName() + "'");
					
				} catch (SecurityException e) {
					throw new ConfigException("Failed to access the initMethod " +  config.getInitMethod() + "() in the class '" + target.getClass().getCanonicalName() + "'");
				} 
			}
			
			try { configCache.getInitMethod().invoke(target); } 
			catch (Exception e) {
				throw new ConfigException("Failed execution the initMethod " + config.getInitMethod() + "() in the class '" + target.getClass().getCanonicalName() + "'");
			}
		}
	}


	public void destroyTarget(Object target) {
		if(config != null && ValueUtils.notEmpty(config.getDestroyMethod())) {
			if(configCache.getDestroyMethod() == null) {
				try {
					configCache.setDestroyMethod(target.getClass().getMethod( config.getDestroyMethod()));
				} catch (NoSuchMethodException e) {
					throw new ConfigException("Failed to found the destroyMethod " +  config.getDestroyMethod() + "() in the class '" + target.getClass().getCanonicalName() + "'");
					
				} catch (SecurityException e) {
					throw new ConfigException("Failed to access the destroyMethod " +  config.getDestroyMethod() + "() in the class '" + target.getClass().getCanonicalName() + "'");
				} 
			}
			
			try { configCache.getDestroyMethod().invoke(target); } 
			catch (Exception e) {
				throw new ConfigException("Failed execution the destroyMethod " + config.getDestroyMethod() + "() in the class '" + target.getClass().getCanonicalName() + "'");
			}
		}
	}
	

}
